package edu.xidian.sei.stream

import java.text.DecimalFormat
import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.locks.ReentrantLock

import edu.xidian.sei.Grid._
import edu.xidian.sei._

import scala.collection.mutable.{ArrayBuffer, HashMap, HashSet}
import scala.math._

/**
  * @author chaostone
  * @author zhiwang
  */
abstract class GridMap(space: Space, reactor: ClusteringReactor, toVisit: Boolean) {
  protected val datas = new HashMap[Index, Grid]
  val count = new AtomicLong(0)
  val tmpcount = new AtomicLong(0)
  var tgap = 0L
  //下一次更新时间段、时间点
  val p = new Array[Int](space.dimension)
  val location2Index = new ArrayBuffer[(Location, Index)]()
  //每一维格子数
  //def N: Long = if (datas.size != 0) datas.size else 1
  var N: BigDecimal = BigDecimal(1)
  var gridNum: BigDecimal = BigDecimal(1)
  //空间中格子总数
  var Cm, Cl, Cmiddle: BigDecimal = BigDecimal(0.0) //判断稀疏、过度、稠密的阈值

  val readyToDelSet = new HashSet[Index]

  val minitor = new ReentrantLock

  private def init(): Unit = {
    val dimension = space.dimension
    for (i <- 0 until dimension) p(i) = ceil(space.upperBound(i) / space.gridLength(i)).toInt
    for (i <- 0 until dimension)
      N *= p(i)
    //N = 100000
    gridNum = N
    Cl = (2.0 / 3.0 * 1.0 / (N * (1 - LAMBDA)))
    Cm = (1.0 / (N * (1 - LAMBDA)))
    //        Cl = 0.7
    //        Cm = 1

    println("dm~~~~~~~~dl" + Cl + "  " + Cm)
    val tmp = (N * (1 - LAMBDA) * Cm - 1) / (N * (1 - LAMBDA) * Cl - 1)
    println("tmp" + tmp)
    //    tgap = (if (tmp <= 0) floor(log((Cl / Cm).toDouble) / log(LAMBDA)) else min(floor(log((Cl / Cm).toDouble) / log(LAMBDA)), floor(log(tmp.toDouble) / log(LAMBDA)))).toLong
    tgap = 10000
    //tgap = floor(log((Cl / Cm).toDouble) / log(LAMBDA)).toLong
    println("11111-----  " + floor(log((Cl / Cm).toDouble) / log(LAMBDA)))
    println("22222-----  " + floor(log(tmp.toDouble) / log(LAMBDA)))
    println("tgap" + tgap)
    Cmiddle = (N * (1 - LAMBDA))

    println("Cmiddle~~~~~~~~" + Cmiddle)
  }

  init()

  def snapshot(tc: Long, tcbefore: Long): Tuple3[BigDecimal, BigDecimal, collection.Map[Index, Grid]]

  def calcIndex(location: Location): Index = {
    val gridLength = space.gridLength
    location match {
      case Coordinate(x, y) => CoIndex(floor(x / gridLength(0)).toInt, floor(y / gridLength(1)).toInt)
      case _ =>
        val dimensions = location.dimensions
        val rs = Array.ofDim[Int](dimensions.size)
        (0 until dimensions.size) foreach { i =>
          rs(i) = floor(dimensions(i) / gridLength(i)).toInt
          //          eachDimension(i)(rs(i)) = if (null == eachDimension(i)(rs(i))) BigDecimal(1) else eachDimension(i)(rs(i)) + 1
        }
        IndexImpl(rs)
    }
  }

  def relocate(center: Location, location: Location, pow_coeff: Double, olddensity: Double, newDensity: Double): Location = {
    if (centerDistance(center, location) > 0.7 * Space.gridDistance) {
      return center
    }

    if (center.dimensionSize == 2) {
      val older = center.asInstanceOf[Coordinate]
      val newer = location.asInstanceOf[Coordinate]
      val newX = (older.x * olddensity * pow_coeff + newer.x) / newDensity
      val newY = (older.y * olddensity * pow_coeff + newer.y) / newDensity
      Coordinate(newX, newY)
    } else {
      val older = center.asInstanceOf[LocationImpl]
      val newer = location.asInstanceOf[LocationImpl]
      var temp = Array.ofDim[Double](older.dimensionSize)
      for (i <- 0 until older.dimensionSize) {
        temp(i) = (older.dimensions(i) * olddensity * pow_coeff + newer.dimensions(i)) / newDensity
      }
      LocationImpl(temp)
    }

  }

  def centerDistance(l1: Location, l2: Location): Double = {
    val dimension = l1.dimensionSize
    val dimension1 = l1.dimensions
    val dimension2 = l2.dimensions
    var d = 0.0
    (0 until dimension).foreach { i =>
      d = d + pow(dimension1(i) - dimension2(i), 2)
    }
    pow(d, 0.5)
  }

  def touch(location: Location)

  def cleanup(lastClusting: Boolean = true): Unit = {
    println(Cl + " Cl+CM " + Cm)
    val writer = new java.io.PrintWriter("location_" + count.get)
    val format = new DecimalFormat("0.00000");
    location2Index.foreach { loc =>
      val formatarray = loc._1.dimensions.map { x => format.format(x) }
      writer.println(formatarray.mkString(",") + "=>" + loc._2.dimensions.mkString(","))
    }
    location2Index.clear()
    writer.close()

    val countNow = count.get
    val countTmp = tmpcount.get()
    val rs = snapshot(countNow, countTmp)
    learn_new_parameters(rs._1, rs._2)
    if (rs._3.size > 1) {

      reactor.clustering(count.get.toString, rs._3, Map(Grid.DensityName -> Cl, "toVisit" -> toVisit))
    } else {
      val writer1 = new java.io.PrintWriter("index2new_" + countNow.toInt.toString + ".txt")
      rs._3.foreach { pair =>
        val oldGrid = pair._2

        writer1.println(oldGrid.id.dimensions.mkString(",") + "=>" + oldGrid.id.dimensions.mkString(","))
      }
      writer1.close()
      val writer = new java.io.PrintWriter("output_" + count.get.toString + ".txt")
      rs._3.foreach { pair =>
        val oldGrid = pair._2
        writer.println(oldGrid.id.dimensions.mkString(",") + "=>" + oldGrid.id.dimensions.mkString(","))
      }
      writer.close()
    }

    reactor.cleanup()
  }

  /**
    * 学习新的参数：Cl，Cm，gap
    *
    * @param s1 非稀疏网格的密度均值
    * @param s2 稀疏网格的密度均值
    */
  def learn_new_parameters(s1: BigDecimal, s2: BigDecimal) {
    N = datas.size
    Cl = 2.0 / 3.0 * 1.0 / (N * (1 - LAMBDA))
    //println((1.0 / (N * (1 - LAMBDA))) + " " + s1)
    Cm = (4.0 * 1.0 / (N * (1 - LAMBDA)) + s1) / 5.0

    println("dm~~~~~~~~dl" + Cl + "  " + Cm + "  N:" + N)
    val tmp = (N * (1 - LAMBDA) * Cm - 1) / (N * (1 - LAMBDA) * Cl - 1)
    val gap = floor(log((Cl / Cm).toDouble) / log(LAMBDA))

    //println("11111-----  " + floor(log((Cl / Cm).toDouble) / log(LAMBDA)))
    //println("22222-----  " + floor(log(tmp.toDouble) / log(LAMBDA)))
    //println("tgap： " + tgap + "   gap: " + gap.toLong + "   tmp: " + tmp)
    tgap += 10000
    //tgap += gap.toLong
  }

  def isNeighbor(x: Index, y: Index): Boolean = {
    var flag = true
    for (i <- (0 until x.dimensions.length))
      flag = flag && (math.abs(x.dimensions(i) - y.dimensions(i)) <= 1)
    flag
  }

  def variance(array: Array[(BigDecimal, Long)]): Double = {
    var ave = 0.0
    array.foreach { i =>
      ave += i._1.toDouble
    }
    ave /= array.length
    var sum = 0.0
    array.foreach { i =>
      val density = i._1.toDouble
      sum += (density - ave) * (density - ave)
    }
    sum /= array.length
    sum
  }

}

object GridMap {

  def apply(space: Space, reactor: ClusteringReactor): GridMap = {
    if (space.dimension <= 4) GridMapFourDimen(space, reactor, false)
    else GridMapMoreDimen(space, reactor, false)
  }
}
